/*
 * PROJECT:         ReactOS api tests
 * LICENSE:         LGPLv2.1+ - See COPYING.LIB in the top level directory
 * PURPOSE:         Test for PrivateExtractIcons
 * PROGRAMMER:      Hermes Belusca-Maito
 *                  Doug Lyons <douglyons@douglyons.com>
 */

#include "precomp.h"
#include <stdio.h>
#include <versionhelpers.h>

BOOL IsValidIcon(HICON hIco)
{
    ICONINFO info = { 0 };

    if (!hIco || !GetIconInfo(hIco, &info))
        return FALSE;

    DeleteObject(info.hbmMask);
    if (info.hbmColor)
        DeleteObject(info.hbmColor);
    return TRUE;
}

BOOL FileExists(PCWSTR FileName)
{
    DWORD Attribute = GetFileAttributesW(FileName);

    return (Attribute != INVALID_FILE_ATTRIBUTES &&
            !(Attribute & FILE_ATTRIBUTE_DIRECTORY));
}

BOOL ResourceToFile(INT i, PCWSTR FileName)
{
    FILE *fout;
    HGLOBAL hData;
    HRSRC hRes;
    PVOID pResLock;
    UINT iSize;

    if (FileExists(FileName))
    {
        /* We should only be using %temp% paths, so deleting here should be OK */
        printf("Deleting '%S' that already exists.\n", FileName);
        DeleteFileW(FileName);
    }

    hRes = FindResourceW(NULL, MAKEINTRESOURCEW(i), MAKEINTRESOURCEW(RT_RCDATA));
    if (hRes == NULL)
    {
        skip("Could not locate resource (%d). Exiting now\n", i);
        return FALSE;
    }

    iSize = SizeofResource(NULL, hRes);

    hData = LoadResource(NULL, hRes);
    if (hData == NULL)
    {
        skip("Could not load resource (%d). Exiting now\n", i);
        return FALSE;
    }

    // Lock the resource into global memory.
    pResLock = LockResource(hData);
    if (pResLock == NULL)
    {
        skip("Could not lock resource (%d). Exiting now\n", i);
        return FALSE;
    }

    fout = _wfopen(FileName, L"wb");
    fwrite(pResLock, iSize, 1, fout);
    fclose(fout);
    return TRUE;
}

static struct
{
    PCWSTR FilePath;
    UINT cIcons;        // Return value of the first icon group extracted (should be 1 if no error)
    UINT cTotalIcons;   // Return value of total icon groups in file
    BOOL bhIconValid;   // Whether or not the returned icon handle is not NULL.
} IconTests[] =
{
    /* Executables with just one icon group */
    {L"notepad.exe", 1, 1, TRUE},
    {L"%SystemRoot%\\System32\\cmd.exe", 1, 1, TRUE},

    /* Executable without icon groups */
    {L"%SystemRoot%\\System32\\autochk.exe", 0, 0, FALSE},

    /* Existing file (shell32 has 233 icon groups in ReactOS only) */
    {L"%SystemRoot%\\System32\\shell32.dll", 1, 233, TRUE},

    /* Non-existing files */
    {L"%SystemRoot%\\non-existent-file.sdf", 0xFFFFFFFF, 0, FALSE},

    /* Executable with 18 icon groups */
    {L"%SystemRoot%\\explorer.exe", 1, 18, TRUE},

    /* Icon group file containing 6 icons */
    {L"%temp%\\sysicon.ico", 1, 1, TRUE},

    /* Icon group file containing one PNG icon and one normal icon */
    {L"%temp%\\ROS.ico", 1, 1, TRUE},

    /* Executable file with bad 'Icon Group' but good 'Icons'.
     * Windows explorer shows the program's icon correctly in WinXP/Win2K3
     * but Windows 7 shows only a default icon. This is analogous
     * to EXE's generated by older Watcom C/C++ versions. */
    {L"%temp%\\cpimg2e.exe", 1, 1, TRUE},
};

static struct
{
    PCWSTR FileName;
    INT ResourceId;
} IconFiles[] =
{
    {L"%temp%\\ROS.ico", IDR_ICONS_PNG},
    {L"%temp%\\sysicon.ico", IDR_ICONS_NORMAL},
    {L"%temp%\\cpimg2e.exe", IDR_EXE_NORMAL}
};

void TestPairExtraction(void);

START_TEST(PrivateExtractIcons)
{
    HICON ahIcon;
    UINT i, aIconId, cIcons, cIcoTotal;
    WCHAR PathBuffer[MAX_PATH];
    UINT Shell32WinIcoCount = IsWindowsVistaOrGreater() ? 326 : 239; /* 239 on W2K3SP2, 326 on Win10 */

    /* Extract icons */
    for (i = 0; i < _countof(IconFiles); ++i)
    {
        ExpandEnvironmentStringsW(IconFiles[i].FileName, PathBuffer, _countof(PathBuffer));

        if (!ResourceToFile(IconFiles[i].ResourceId, PathBuffer))
            goto Cleanup;
    }

    TestPairExtraction();

    for (i = 0; i < _countof(IconTests); ++i)
    {
        /* Get total number of icon groups in file.
         * None of the hard numbers in the function matter since we have
         * two NULLs for the Icon Handle and Count to be set. */
        cIcoTotal = PrivateExtractIconsW(IconTests[i].FilePath, 0, 16, 16, NULL, NULL, 0, 0);
        ok((i == 3 ?
              cIcoTotal > 232 && cIcoTotal <= Shell32WinIcoCount :  /* shell32 case: ROS has 233, Windows has >= 239 icon groups. */
              cIcoTotal == IconTests[i].cTotalIcons),
           "PrivateExtractIconsW(%u): "
           "got %u, expected %u\n", i, cIcoTotal, IconTests[i].cTotalIcons);

        /* Always test extraction of the FIRST icon (index 0) */
        ahIcon = (HICON)UlongToHandle(0xdeadbeef);
        aIconId = 0xdeadbeef;
        cIcons = PrivateExtractIconsW(IconTests[i].FilePath, 0, 16, 16, &ahIcon, &aIconId, 1, 0);
        ok(cIcons == IconTests[i].cIcons, "PrivateExtractIconsW(%u): got %u, expected %u\n", i, cIcons, IconTests[i].cIcons);
        ok(ahIcon != (HICON)UlongToHandle(0xdeadbeef), "PrivateExtractIconsW(%u): icon not set\n", i);
        ok((IconTests[i].bhIconValid && ahIcon) || (!IconTests[i].bhIconValid && !ahIcon),
            "PrivateExtractIconsW(%u): icon expected to be %s, but got 0x%p\n",
            i, IconTests[i].bhIconValid ? "valid" : "not valid", ahIcon);
        if (cIcons == 0xFFFFFFFF)
        {
            ok(aIconId == 0xdeadbeef,
               "PrivateExtractIconsW(%u): id should not be set to 0x%x\n",
               i, aIconId);
        }
        else
        {
            ok(aIconId != 0xdeadbeef, "PrivateExtractIconsW(%u): id not set\n", i);
        }
        if (ahIcon && ahIcon != (HICON)UlongToHandle(0xdeadbeef))
            DestroyIcon(ahIcon);
    }

Cleanup:
    for (i = 0; i < _countof(IconFiles); ++i)
    {
        ExpandEnvironmentStringsW(IconFiles[i].FileName, PathBuffer, _countof(PathBuffer));
        DeleteFileW(PathBuffer);
    }
}

static const struct tagPAIRSTESTS
{
    BYTE InCount;
    BYTE UseHigh; // Extract a pair (high and low word sizes)
    BYTE Library;
    BYTE ReturnNT5;
    BYTE CountNT5;
    BYTE ReturnNT6;
    BYTE CountNT6;
} g_pairs[] =
{
    { 0, 0, 0,     1, 1, 1, 1 },
    { 0, 0, 1,     0, 0, 0, 0 },
    { 0, 1, 0,     1, 2, 1, 2 },
    { 0, 1, 1,     0, 0, 0, 0 },
    { 1, 0, 0,     1, 1, 1, 1 },
    { 1, 0, 1,     1, 1, 1, 1 },
    { 1, 1, 0,     1, 2, 1, 2 },
    { 1, 1, 1,     2, 2, 0, 0 },
    { 2, 0, 0,     1, 1, 1, 1 },
    { 2, 0, 1,     2, 2, 2, 2 },
    { 2, 1, 0,     1, 2, 1, 2 },
    { 2, 1, 1,     2, 2, 2, 2 }, // This is the only way to extract a pair from a PE on NT6!
    { 3, 0, 0,     1, 1, 1, 1 },
    { 3, 0, 1,     3, 3, 3, 3 },
    { 3, 1, 0,     1, 2, 1, 2 },
    { 3, 1, 1,     4, 4, 0, 0 },
    { 4, 0, 0,     1, 1, 1, 1 },
    { 4, 0, 1,     4, 4, 4, 4 },
    { 4, 1, 0,     1, 2, 1, 2 },
    { 4, 1, 1,     4, 4, 4, 4 }
};

void TestPairExtraction(void)
{
    const HICON hInvalidIcon = (HICON)(INT_PTR)-2;
    const BOOL IsNT6 = IsWindowsVistaOrGreater();
    for (UINT i = 0; i < _countof(g_pairs); ++i)
    {
        UINT j, Count, ExpectedCount;
        int RetVal, ExpectedRet;
        UINT IcoSize = MAKELONG(32, g_pairs[i].UseHigh ? 16 : 0);
        PCWSTR pszPath = g_pairs[i].Library ? L"%SystemRoot%\\system32\\shell32.dll" : L"%temp%\\sysicon.ico";
        HICON hIcons[8];
        for (j = 0; j < _countof(hIcons); ++j)
            hIcons[j] = hInvalidIcon;

        RetVal = PrivateExtractIconsW(pszPath, 0, IcoSize, IcoSize, hIcons, NULL, g_pairs[i].InCount, 0);
        for (j = 0, Count = 0; j < _countof(hIcons); ++j)
        {
            if (hIcons[j] != hInvalidIcon && IsValidIcon(hIcons[j]))
            {
                DestroyIcon(hIcons[j]);
                ++Count;
            }
        }

        ExpectedRet = !IsNT6 ? g_pairs[i].ReturnNT5 : g_pairs[i].ReturnNT6;
        ExpectedCount = !IsNT6 ? g_pairs[i].CountNT5 : g_pairs[i].CountNT6;
        ok(RetVal == ExpectedRet, "Test %u: RetVal must be %d but got %d\n", i, ExpectedRet, RetVal);
        ok(Count == ExpectedCount, "Test %u: Count must be %u but got %u\n", i, ExpectedCount, Count);
    }
}
